package com.xiashitech.agent.instrumentation.base.propagator;

import javax.annotation.Nullable;
import java.util.BitSet;

public class XSElement {

    private static final BitSet EXCLUDED_KEY_CHARS = new BitSet(128);
    private static final BitSet EXCLUDED_VALUE_CHARS = new BitSet(128);

    static {
        for (char c :
                new char[] {
                        '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}'
                }) {
            EXCLUDED_KEY_CHARS.set(c);
        }
        for (char c : new char[] {'"', ',', ';', '\\'}) {
            EXCLUDED_VALUE_CHARS.set(c);
        }
    }

    private final BitSet excluded;

    private boolean leadingSpace;
    private boolean readingValue;
    private boolean trailingSpace;
    private int start;
    private int end;
    @Nullable
    private String value;

    static XSElement createKeyElement() {
        return new XSElement(EXCLUDED_KEY_CHARS);
    }

    static XSElement createValueElement() {
        return new XSElement(EXCLUDED_VALUE_CHARS);
    }

    /**
     * Constructs element instance.
     *
     * @param excluded characters that are not allowed for this type of an element
     */
    private XSElement(BitSet excluded) {
        this.excluded = excluded;
        reset(0);
    }

    @Nullable
    String getValue() {
        return value;
    }

    void reset(int start) {
        this.start = start;
        leadingSpace = true;
        readingValue = false;
        trailingSpace = false;
        value = null;
    }

    boolean tryTerminating(int index, String header) {
        if (this.readingValue) {
            markEnd(index);
        }
        if (this.trailingSpace) {
            setValue(header);
            return true;
        } else {
            // leading spaces - no content, invalid
            return false;
        }
    }

    private void markEnd(int end) {
        this.end = end;
        this.readingValue = false;
        trailingSpace = true;
    }

    private void setValue(String header) {
        this.value = header.substring(this.start, this.end);
    }

    boolean tryNextChar(char character, int index) {
        if (isWhitespace(character)) {
            return tryNextWhitespace(index);
        } else if (isExcluded(character)) {
            return false;
        } else {
            return tryNextTokenChar(index);
        }
    }

    private static boolean isWhitespace(char character) {
        return character == ' ' || character == '\t';
    }

    private boolean tryNextWhitespace(int index) {
        if (readingValue) {
            markEnd(index);
        }
        return true;
    }

    private boolean isExcluded(char character) {
        return (character <= 32 || character >= 127 || excluded.get(character));
    }

    private boolean tryNextTokenChar(int index) {
        if (leadingSpace) {
            markStart(index);
        }
        return !trailingSpace;
    }

    private void markStart(int start) {
        this.start = start;
        readingValue = true;
        leadingSpace = false;
    }
}

